
// This file is part of the mstp-lib library, available at http://code.google.com/p/mstp-lib/ 
// Copyright (c) 2011-2014 Adrian Gostin, distributed under the GNU General Public License v3.

#include "DotNetWrapper.h"
#include <stdlib.h>
#include <string.h>

using namespace System::Diagnostics;
using namespace System::Globalization;
using namespace System::Runtime::InteropServices;
using namespace System::Text;
using namespace System::Threading;

void AssertFunc (const char* sourceFile, int lineNumber)
{
    System::Diagnostics::Debugger::Break ();
}

namespace STP 
{
	BridgeWrapper::BridgeWrapper (UInt32 portCount, UInt32 treeCount, StpVersion protocolVersion, UInt64 bridgeAddress, UInt32 debugLogBufferSize, ICallbackHandler^ callbackHandler)
	{
		this->EnableLearningCallback			= gcnew EnableLearningDelegate			(this, &BridgeWrapper::OnMstpLibEnableLearning);
		this->EnableForwardingCallback		= gcnew EnableForwardingDelegate		(this, &BridgeWrapper::OnMstpLibEnableForwarding);
		this->TransmitGetBufferCallback		= gcnew TransmitGetBufferDelegate		(this, &BridgeWrapper::OnMstpLibTransmitGetBuffer);
		this->TransmitReleaseBufferCallback	= gcnew TransmitReleaseBufferDelegate	(this, &BridgeWrapper::OnMstpLibTransmitReleaseBuffer);
		this->FlushFdbCallback				= gcnew FlushFdbDelegate				(this, &BridgeWrapper::OnMstpLibFdbFlush);
		this->DebugStrOutCallback			= gcnew DebugStrOutDelegate				(this, &BridgeWrapper::OnMstpLibDebugStrOut);
		this->AllocAndZeroMemoryCallback		= gcnew AllocAndZeroMemoryDelegate		(this, &BridgeWrapper::OnMstpLibAlloc);
		this->FreeMemoryCallback				= gcnew FreeMemoryDelegate				(this, &BridgeWrapper::OnMstpLibFree);

		STP_CALLBACKS stpCallbacks = 
		{
			(STP_CALLBACK_ENABLE_LEARNING)			Marshal::GetFunctionPointerForDelegate (EnableLearningCallback).ToPointer (),
			(STP_CALLBACK_ENABLE_FORWARDING)		Marshal::GetFunctionPointerForDelegate (EnableForwardingCallback).ToPointer (),
			(STP_CALLBACK_TRANSMIT_GET_BUFFER)		Marshal::GetFunctionPointerForDelegate (TransmitGetBufferCallback).ToPointer (),
			(STP_CALLBACK_TRANSMIT_RELEASE_BUFFER)	Marshal::GetFunctionPointerForDelegate (TransmitReleaseBufferCallback).ToPointer (),
			(STP_CALLBACK_FLUSH_FDB)				Marshal::GetFunctionPointerForDelegate (FlushFdbCallback).ToPointer (),
			(STP_CALLBACK_DEBUG_STR_OUT)			Marshal::GetFunctionPointerForDelegate (DebugStrOutCallback).ToPointer (),
			(STP_CALLBACK_ON_TOPOLOGY_CHANGED)		nullptr,
			(STP_CALLBACK_ALLOC_AND_ZERO_MEMORY)	Marshal::GetFunctionPointerForDelegate (AllocAndZeroMemoryCallback).ToPointer (),
			(STP_CALLBACK_FREE_MEMORY)				Marshal::GetFunctionPointerForDelegate (FreeMemoryCallback).ToPointer (),
		};

		unsigned char addressBytes [6];
		addressBytes [0] = (unsigned char) (bridgeAddress >> 40);
		addressBytes [1] = (unsigned char) (bridgeAddress >> 32);
		addressBytes [2] = (unsigned char) (bridgeAddress >> 24);
		addressBytes [3] = (unsigned char) (bridgeAddress >> 16);
		addressBytes [4] = (unsigned char) (bridgeAddress >> 8);
		addressBytes [5] = (unsigned char) bridgeAddress;

		_bpduBeingTransmitted = malloc (1536);
		_callbackHandler = callbackHandler;
		_bridge = STP_CreateBridge (portCount, treeCount, &stpCallbacks, (STP_VERSION) protocolVersion, addressBytes, debugLogBufferSize);
	}

	BridgeWrapper::~BridgeWrapper ()
	{
		if (_disposed)
			return;

		// dispose here managed data, if any

		// and now call the finalizer to dispose of unmanaged data (BRIDGE* in our case)
		this->!BridgeWrapper ();
		_disposed = true;
	}

	BridgeWrapper::!BridgeWrapper ()
	{
		STP_DestroyBridge (_bridge);
		_bridge = nullptr;

		free (_bpduBeingTransmitted);
		_bpduBeingTransmitted = nullptr;
	}

	// -------------------------------------------------------------------------

	void BridgeWrapper::OnMstpLibEnableLearning (BRIDGE* bridge, unsigned int portIndex, unsigned int treeIndex, bool enable)
	{
		Debug::Assert (bridge == _bridge);
		_callbackHandler->EnableLearning (this, portIndex, treeIndex, enable);
	}

	void BridgeWrapper::OnMstpLibEnableForwarding (BRIDGE* bridge, unsigned int portIndex, unsigned int treeIndex, bool enable)
	{
		Debug::Assert (bridge == _bridge);
		_callbackHandler->EnableForwarding (this, portIndex, treeIndex, enable);
	}

	void* BridgeWrapper::OnMstpLibTransmitGetBuffer (BRIDGE* bridge, unsigned int portIndex, unsigned int bpduSize)
	{
		Debug::Assert (bridge == _bridge);

		_bpduBeingTransmittedSourcePort = portIndex;
		_bpduBeingTransmittedLength = bpduSize;

		return _bpduBeingTransmitted;
	}

	void BridgeWrapper::OnMstpLibTransmitReleaseBuffer	(BRIDGE* bridge)
	{
		Debug::Assert (bridge == _bridge);

		array<unsigned char>^ managed = gcnew array<unsigned char> (_bpduBeingTransmittedLength);
		Marshal::Copy ((IntPtr) _bpduBeingTransmitted, managed, 0, _bpduBeingTransmittedLength);

		_callbackHandler->Transmit (this, _bpduBeingTransmittedSourcePort, managed);
	}

	void BridgeWrapper::OnMstpLibFdbFlush (BRIDGE* bridge, unsigned int portIndex, unsigned int treeIndex, STP_FLUSH_FDB_TYPE flushType)
	{
		Debug::Assert (bridge == _bridge);

		_callbackHandler->FdbFlush (this, portIndex, treeIndex, (FlushFdbType) flushType);
	}

	void BridgeWrapper::OnMstpLibDebugStrOut (BRIDGE* bridge, int portIndex, int treeIndex, const char* str, bool flush)
	{
		Debug::Assert (bridge == _bridge);

		_callbackHandler->DebugStrOut (this, portIndex, treeIndex, gcnew String (str), flush);
	}

	void* BridgeWrapper::OnMstpLibAlloc (unsigned int size)
	{
		void* result = malloc (size);
		if (result == nullptr)
			throw gcnew OutOfMemoryException ();

		memset (result, 0, size);

		_bytesAllocated += size;

		return result;
	}

	void BridgeWrapper::OnMstpLibFree (void* p)
	{
		free (p);
	}

	// -------------------------------------------------------------------------

	void BridgeWrapper::Start (unsigned int timestamp)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_StartBridge (_bridge, timestamp);
	}

	void BridgeWrapper::Stop (unsigned int timestamp)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_StopBridge (_bridge, timestamp);
	}

	bool BridgeWrapper::IsStarted ()
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_IsBridgeStarted (_bridge);
	}

	void BridgeWrapper::SetPortPriority (unsigned int portIndex, unsigned int treeIndex, unsigned char portPriority, unsigned int timestamp)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_SetPortPriority (_bridge, portIndex, treeIndex, portPriority, timestamp);
	}

	unsigned char BridgeWrapper::GetPortPriority (unsigned int portIndex, unsigned int treeIndex)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetPortPriority (_bridge, portIndex, treeIndex);
	}

	unsigned short BridgeWrapper::GetPortIdentifier (unsigned int portIndex, unsigned int treeIndex)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetPortIdentifier (_bridge, portIndex, treeIndex);
	}

	bool BridgeWrapper::GetPortEnabled (unsigned int portIndex)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetPortEnabled (_bridge, portIndex);
	}

	PortRole BridgeWrapper::GetPortRole (unsigned int portIndex, unsigned int treeIndex)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return (PortRole) STP_GetPortRole (_bridge, portIndex, treeIndex);
	}

	bool BridgeWrapper::GetPortLearning (unsigned int portIndex, unsigned int treeIndex)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetPortLearning (_bridge, portIndex, treeIndex);
	}

	bool BridgeWrapper::GetPortForwarding (unsigned int portIndex, unsigned int treeIndex)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetPortForwarding (_bridge, portIndex, treeIndex);
	}

	bool BridgeWrapper::GetPortOperEdge (unsigned int portIndex)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetPortOperEdge (_bridge, portIndex);
	}

	bool BridgeWrapper::GetPortOperPointToPointMAC (unsigned int portIndex)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetPortOperPointToPointMAC (_bridge, portIndex);
	}

	unsigned int BridgeWrapper::PortCount::get ()
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetPortCount (_bridge);
	}

	unsigned int BridgeWrapper::TreeCount::get ()
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetTreeCount (_bridge);
	}

	STP::StpVersion BridgeWrapper::GetStpVersion ()
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return (STP::StpVersion) STP_GetStpVersion (_bridge);
	}

	void BridgeWrapper::SetStpVersion (STP::StpVersion version)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_SetStpVersion (_bridge, (STP_VERSION) version);
	}

	UInt64 BridgeWrapper::GetBridgeAddress ()
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		const unsigned char* address = STP_GetBridgeAddress (_bridge);
			
		return (((UInt64) address [0]) << 40)
			|  (((UInt64) address [1]) << 32)
			|  (((UInt64) address [2]) << 24)
			|  (((UInt64) address [3]) << 16)
			|  (((UInt64) address [4]) << 8)
			|  ((UInt64) address [5]);
	}

	void BridgeWrapper::SetBridgeAddress (UInt64 address)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		unsigned char bytes [6];
		bytes [0] = (unsigned char) (address >> 40);
		bytes [1] = (unsigned char) (address >> 32);
		bytes [2] = (unsigned char) (address >> 24);
		bytes [3] = (unsigned char) (address >> 16);
		bytes [4] = (unsigned char) (address >> 8);
		bytes [5] = (unsigned char) address;

		STP_SetBridgeAddress (_bridge, bytes, Environment::TickCount);
	}

	void BridgeWrapper::SetBridgePriority (unsigned int treeIndex, unsigned short bridgePriority, unsigned int timestamp)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_SetBridgePriority (_bridge, treeIndex, bridgePriority, timestamp);
	}

	unsigned short BridgeWrapper::GetBridgePriority (unsigned int treeIndex)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetBridgePriority (_bridge, treeIndex);
	}

	void BridgeWrapper::OnPortDisabled (unsigned int portIndex, unsigned int timestamp)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_OnPortDisabled (_bridge, portIndex, timestamp);
	}

	void BridgeWrapper::OnPortEnabled (unsigned int portIndex, unsigned int linkSpeedMegabitsPerSecond, bool fullDuplex, unsigned int timestamp)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_OnPortEnabled (_bridge, portIndex, linkSpeedMegabitsPerSecond, fullDuplex, timestamp);
	}

	void BridgeWrapper::EnableLogging (bool enable)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_EnableLogging (_bridge, enable);
	}

	void BridgeWrapper::OnOneSecondTick (unsigned int timestamp)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_OnOneSecondTick (_bridge, timestamp);
	}

	void BridgeWrapper::OnBpduReceived (unsigned int portIndex, array<unsigned char>^ bpdu, unsigned int timestamp)
	{
		IntPtr bpduUnmanaged = IntPtr::Zero;
		try
		{
			if (_disposed)
				throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

			bpduUnmanaged = Marshal::AllocHGlobal (bpdu->Length);
			Marshal::Copy (bpdu, 0, bpduUnmanaged, bpdu->Length);
			STP_OnBpduReceived (_bridge, portIndex, (unsigned char*) (void*) bpduUnmanaged, bpdu->Length, timestamp);
		}
		finally
		{
			if (bpduUnmanaged != IntPtr::Zero)
				Marshal::FreeHGlobal (bpduUnmanaged);
		}
	}

	unsigned char BridgeWrapper::GetTreeIndexFromVlanNumber (unsigned short vlanNumber)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetTreeIndexFromVlanNumber (_bridge, vlanNumber);
	}

	String^ BridgeWrapper::GetMstConfigName ()
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		char name [33];
		STP_GetMstConfigName (_bridge, name);
		return gcnew String (name);
	}

	String^ BridgeWrapper::GetMstConfigDefaultName ()
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		char name [33];
		STP_GetMstConfigDefaultName (_bridge, name);
		return gcnew String (name);
	}

	bool BridgeWrapper::GetMstConfigDefaultNameUsed ()
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetMstConfigDefaultNameUsed (_bridge);
	}

	void BridgeWrapper::SetMstConfigName (String^ name, unsigned int timestamp)
	{
		if (name->Length > 32)
			throw gcnew ArgumentException ("Name must be no longer than 32 characters.");

		char str [33];
		memset (str, 0, 33);
		for (int i = 0; i < name->Length; i++)
		{
			Char ch = name [i];
			if ((ch < 32) || (ch >= 128))
				throw gcnew ArgumentException ("Name must contain ASCII characters (32..127)");

			str [i] = (char) ch;
		}

		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_SetMstConfigName (_bridge, str, timestamp);
	}

	void BridgeWrapper::SetMstConfigDefaultName (unsigned int timestamp)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_ResetMstConfigName (_bridge, timestamp);
	}

	unsigned short BridgeWrapper::GetMstConfigRevisionLevel ()
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetMstConfigRevisionLevel (_bridge);
	}

	void BridgeWrapper::SetMstConfigRevisionLevel (unsigned short revisionLevel, unsigned int timestamp)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_SetMstConfigRevisionLevel (_bridge, revisionLevel, timestamp);
	}

	array<unsigned char>^ BridgeWrapper::GetMstConfigTable ()
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		unsigned char mstids [4094];
		STP_GetMstConfigTable (_bridge, mstids);
		array<unsigned char>^ result = gcnew array<unsigned char> (4094);
		Marshal::Copy ((IntPtr) mstids, result, 0, 4094);
		return result;
	}

	void BridgeWrapper::SetMstConfigTable (array<unsigned char>^ table, unsigned int timestamp)
	{
		unsigned char mstids [4094];
		for (int i = 0; i < 4094; i++)
			mstids [i] = table [i];

		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_SetMstConfigTableAndComputeDigest1 (_bridge, mstids, timestamp);
	}

	void BridgeWrapper::SetPortAdminEdge (unsigned int portIndex, bool adminEdge)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		STP_SetPortAdminEdge (_bridge, portIndex, adminEdge);
	}

	bool BridgeWrapper::GetPortAdminEdge (unsigned int portIndex)
	{
		if (_disposed)
			throw gcnew ObjectDisposedException ("Cannot access a disposed BridgeWrapper");

		return STP_GetPortAdminEdge (_bridge, portIndex);
	}

	unsigned int BridgeWrapper::BytesAllocated::get ()
	{
		return _bytesAllocated;
	}
}
